/*
 * This file is part of aion-emu <aion-emu.com>.
 *
 *  aion-emu is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  aion-emu is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with aion-emu. If not, see <http://www.gnu.org/licenses/>.
 */
package com.aionengine.gameserver.model.gameobjects;

import com.aionengine.gameserver.ai2.AI2Engine;
import com.aionengine.gameserver.ai2.AITemplate;
import com.aionengine.gameserver.ai2.poll.AIQuestion;
import com.aionengine.gameserver.configs.main.AIConfig;
import com.aionengine.gameserver.controllers.NpcController;
import com.aionengine.gameserver.controllers.movement.NpcMoveController;
import com.aionengine.gameserver.dataholders.DataManager;
import com.aionengine.gameserver.model.CreatureType;
import com.aionengine.gameserver.model.Race;
import com.aionengine.gameserver.model.TribeClass;
import com.aionengine.gameserver.model.drop.NpcDrop;
import com.aionengine.gameserver.model.gameobjects.player.Player;
import com.aionengine.gameserver.model.gameobjects.siege.SiegeNpc;
import com.aionengine.gameserver.model.skill.NpcSkillList;
import com.aionengine.gameserver.model.stats.container.NpcGameStats;
import com.aionengine.gameserver.model.stats.container.NpcLifeStats;
import com.aionengine.gameserver.model.templates.item.ItemAttackType;
import com.aionengine.gameserver.model.templates.npc.NpcRating;
import com.aionengine.gameserver.model.templates.npc.NpcTemplate;
import com.aionengine.gameserver.model.templates.npc.NpcTemplateType;
import com.aionengine.gameserver.model.templates.npcshout.NpcShout;
import com.aionengine.gameserver.model.templates.npcshout.ShoutEventType;
import com.aionengine.gameserver.model.templates.npcshout.ShoutType;
import com.aionengine.gameserver.model.templates.spawns.SpawnTemplate;
import com.aionengine.gameserver.network.aion.serverpackets.SM_LOOKATOBJECT;
import com.aionengine.gameserver.network.aion.serverpackets.SM_SYSTEM_MESSAGE;
import com.aionengine.gameserver.services.TribeRelationService;
import com.aionengine.gameserver.spawnengine.WalkerGroup;
import com.aionengine.gameserver.spawnengine.WalkerGroupShift;
import com.aionengine.gameserver.utils.MathUtil;
import com.aionengine.gameserver.utils.PacketSendUtility;
import com.aionengine.gameserver.utils.ThreadPoolManager;
import com.aionengine.gameserver.world.WorldPosition;
import com.aionengine.gameserver.world.WorldType;
import com.google.common.base.Preconditions;
import org.apache.commons.lang.StringUtils;

import java.util.Iterator;

/**
 * This class is a base class for all in-game NPCs, what includes: monsters and npcs that player can talk to (aka
 * Citizens)
 *
 * @author Luno
 */
public class Npc extends Creature {

    private WalkerGroup walkerGroup;
    private boolean isQuestBusy = false;
    private NpcSkillList skillList;
    private WalkerGroupShift walkerGroupShift;
    private long lastShoutedSeconds;
    private String masterName = StringUtils.EMPTY;
    private int creatorId = 0;
    private int townId;
    private ItemAttackType attacktype = ItemAttackType.PHYSICAL;
    private int aRange = getObjectTemplate().getAggroRange();

    public Npc(int objId, NpcController controller, SpawnTemplate spawnTemplate, NpcTemplate objectTemplate) {
        this(objId, controller, spawnTemplate, objectTemplate, objectTemplate.getLevel());
    }

    public Npc(int objId, NpcController controller, SpawnTemplate spawnTemplate, NpcTemplate objectTemplate, byte level) {
        super(objId, controller, spawnTemplate, objectTemplate, new WorldPosition(spawnTemplate.getWorldId()));
        Preconditions.checkNotNull(objectTemplate, "Npcs should be based on template");
        controller.setOwner(this);
        moveController = new NpcMoveController(this);
        skillList = new NpcSkillList(this);
        setupStatContainers(level);

        boolean aiOverride = false;
        if (spawnTemplate.getModel() != null) {
            if (spawnTemplate.getModel().getAi() != null) {
                aiOverride = true;
                AI2Engine.getInstance().setupAI(spawnTemplate.getModel().getAi(), this);
            }
        }

        if (!aiOverride)
            AI2Engine.getInstance().setupAI(objectTemplate.getAi(), this);

        lastShoutedSeconds = System.currentTimeMillis() / 1000;
    }

    @Override
    public NpcMoveController getMoveController() {
        return (NpcMoveController) super.getMoveController();
    }

    /**
     * @param level
     */
    protected void setupStatContainers(byte level) {
        setGameStats(new NpcGameStats(this));
        setLifeStats(new NpcLifeStats(this));
    }

    @Override
    public NpcTemplate getObjectTemplate() {
        return (NpcTemplate) objectTemplate;
    }

    @Override
    public String getName() {
        return getObjectTemplate().getName();
    }

    public int getNpcId() {
        return getObjectTemplate().getTemplateId();
    }

    @Override
    public byte getLevel() {
        return getObjectTemplate().getLevel();
    }

    @Override
    public NpcLifeStats getLifeStats() {
        return (NpcLifeStats) super.getLifeStats();
    }

    @Override
    public NpcGameStats getGameStats() {
        return (NpcGameStats) super.getGameStats();
    }

    @Override
    public NpcController getController() {
        return (NpcController) super.getController();
    }

    @Override
    public ItemAttackType getAttackType() {
        return ai2.modifyAttackType(attacktype);
    }

    public NpcSkillList getSkillList() {
        return this.skillList;
    }

    public boolean hasWalkRoutes() {
        return getSpawn().getWalkerId() != null || (getSpawn().hasRandomWalk() && AIConfig.ACTIVE_NPC_MOVEMENT);
    }

    @Override
    public TribeClass getTribe() {
        TribeClass transformTribe = getTransformModel().getTribe();
        if (transformTribe != null) {
            return transformTribe;
        }
        return this.getObjectTemplate().getTribe();
    }

    @Override
    public TribeClass getBaseTribe() {
        return DataManager.TRIBE_RELATIONS_DATA.getBaseTribe(getTribe());
    }

    public int getAggroRange() {
        return ai2.modifyARange(aRange);
    }

    /**
     * Check whether npc located near initial spawn location
     *
     * @return true or false
     */
    public boolean isAtSpawnLocation() {
        return getDistanceToSpawnLocation() < 3;
    }

    @Override
    public boolean isEnemy(Creature creature) {
        return creature.isEnemyFrom(this) || this.isEnemyFrom(creature);
    }

    @Override
    public boolean isEnemyFrom(Creature creature) {
        return TribeRelationService.isAggressive(creature, this) || TribeRelationService.isHostile(creature, this);
    }

    @Override
    public boolean isEnemyFrom(Npc npc) {
        return TribeRelationService.isAggressive(this, npc) || TribeRelationService.isHostile(this, npc);
    }

    @Override
    public boolean isEnemyFrom(Player player) {
        return player.isEnemyFrom(this);
    }

    @Override
    public int getType(Creature creature) {
        int typeForPlayer = -1;
        if (TribeRelationService.isNone(this, creature))
            typeForPlayer = CreatureType.PEACE.getId();
        else if (TribeRelationService.isAggressive(this, creature))
            typeForPlayer = CreatureType.AGGRESSIVE.getId();
        else if (TribeRelationService.isHostile(this, creature))
            typeForPlayer = CreatureType.ATTACKABLE.getId();
        else if (TribeRelationService.isFriend(this, creature) || TribeRelationService.isNeutral(this, creature))
            typeForPlayer = CreatureType.FRIEND.getId();
        else if (TribeRelationService.isSupport(this, creature))
            typeForPlayer = CreatureType.SUPPORT.getId();
        /**if (typeForPlayer == CreatureType.PEACE.getId() || typeForPlayer == CreatureType.SUPPORT.getId()) {
            if (getObjectTemplate().isDialogNpc())
                typeForPlayer = CreatureType.FRIEND.getId();
        }*/
        return typeForPlayer;
    }

    /**
     * @return distance to spawn location
     */
    public double getDistanceToSpawnLocation() {
        return MathUtil.getDistance(getSpawn().getX(), getSpawn().getY(), getSpawn().getZ(), getX(), getY(), getZ());
    }

    @Override
    public int getSeeState() {
        int skillSeeState = super.getSeeState();
        int congenitalSeeState = getObjectTemplate().getRating().getCongenitalSeeState().getId();
        return Math.max(skillSeeState, congenitalSeeState);
    }

    public boolean getIsQuestBusy() {
        return isQuestBusy;
    }

    public void setIsQuestBusy(boolean busy) {
        isQuestBusy = busy;
    }

    /**
     * @return Name of the Master
     */
    public String getMasterName() {
        return masterName;
    }

    public void setMasterName(String masterName) {
        this.masterName = masterName;
    }

    /**
     * @return UniqueId of the VisibleObject which created this Npc (could be player or house)
     */
    public int getCreatorId() {
        return creatorId;
    }

    public void setCreatorId(int creatorId) {
        this.creatorId = creatorId;
    }

    public int getTownId() {
        return townId;
    }

    public void setTownId(int townId) {
        this.townId = townId;
    }

    public VisibleObject getCreator() {
        return null;
    }

    @Override
    public void setTarget(VisibleObject creature) {
        if (getTarget() != creature) {
            super.setTarget(creature);
            super.clearAttackedCount();
            getGameStats().renewLastChangeTargetTime();
            if (!getLifeStats().isAlreadyDead()) {
                PacketSendUtility.broadcastPacket(this, new SM_LOOKATOBJECT(this));
            }
        }
    }

    public void setWalkerGroup(WalkerGroup wg) {
        this.walkerGroup = wg;
    }

    public WalkerGroup getWalkerGroup() {
        return walkerGroup;
    }

    public void setWalkerGroupShift(WalkerGroupShift shift) {
        this.walkerGroupShift = shift;
    }

    public WalkerGroupShift getWalkerGroupShift() {
        return walkerGroupShift;
    }

    @Override
    public boolean isFlag() {
        return getObjectTemplate().getNpcTemplateType().equals(NpcTemplateType.FLAG);
    }


    public boolean isBoss() {
        return getObjectTemplate().getRating() == NpcRating.HERO || getObjectTemplate().getRating() == NpcRating.LEGENDARY;
    }

    public boolean hasStatic() {
        return getSpawn().getStaticId() != 0;
    }

    @Override
    public Race getRace() {
        return this.getObjectTemplate().getRace();
    }

    public NpcDrop getNpcDrop() {
        return getObjectTemplate().getNpcDrop();
    }

    public void setNpcType(int newType) {
        type = newType;
    }

    public boolean isRewardAP() {
		if (this instanceof SiegeNpc) {
			return true;
		}
		else if (this.getWorldType() == WorldType.ABYSS) {
			return true;
		}
		else if (this.getAi2().ask(AIQuestion.SHOULD_REWARD_AP).isPositive()) {
			return true;
		}
		else if (this.getWorldType() == WorldType.BALAUREA) {
			return getRace() == Race.DRAKAN || getRace() == Race.LIZARDMAN;
		}
		return false;
	}

    public boolean mayShout(int delaySeconds) {
        if (!DataManager.NPC_SHOUT_DATA.hasAnyShout(getPosition().getMapId(), getNpcId()))
            return false;
        return (System.currentTimeMillis() - lastShoutedSeconds) / 1000 >= delaySeconds;
    }

    public void shout(final NpcShout shout, final Creature target, final Object param, int delaySeconds) {
        if (shout.getWhen() != ShoutEventType.DIED && shout.getWhen() != ShoutEventType.BEFORE_DESPAWN
                && getLifeStats().isAlreadyDead() || !mayShout(delaySeconds))
            return;

        if (shout.getPattern() != null
                && !((AITemplate) getAi2()).onPatternShout(shout.getWhen(), shout.getPattern(), shout.getSkillNo()))
            return;

        final int shoutRange = getObjectTemplate().getMinimumShoutRange();
        if (shout.getShoutType() == ShoutType.SAY && !(target instanceof Player) || target != null
                && !MathUtil.isIn3dRange(target, this, shoutRange))
            return;

        final Npc thisNpc = this;
        final SM_SYSTEM_MESSAGE message = new SM_SYSTEM_MESSAGE(true, shout.getStringId(), getObjectId(), 1, param);
        lastShoutedSeconds = System.currentTimeMillis() / 1000;

        ThreadPoolManager.getInstance().schedule(new Runnable() {

            @Override
            public void run() {
                if (thisNpc.getLifeStats().isAlreadyDead() && shout.getWhen() != ShoutEventType.DIED
                        && shout.getWhen() != ShoutEventType.BEFORE_DESPAWN)
                    return;

                // message for the specific player (when IDLE we are already broadcasting!!!)
                if (shout.getShoutType() == ShoutType.SAY || shout.getWhen() == ShoutEventType.IDLE) {
                    // [RR] Should we have lastShoutedSeconds separated from broadcasts (??)
                    PacketSendUtility.sendPacket((Player) target, message);
                } else {
                    Iterator<Player> iter = thisNpc.getKnownList().getKnownPlayers().values().iterator();
                    while (iter.hasNext()) {
                        Player kObj = iter.next();
                        if (kObj.getLifeStats().isAlreadyDead() || !kObj.isOnline())
                            continue;
                        if (MathUtil.isIn3dRange(kObj, thisNpc, shoutRange))
                            PacketSendUtility.sendPacket(kObj, message);
                    }
                }
            }
        }, delaySeconds * 1000);
    }

}
